package ca.uvic.cs.cogz.stats;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;

import javax.swing.JButton;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;

import ca.uvic.cs.cogz.mapping.SuggestionsManager;
import ca.uvic.cs.cogz.mapping.ontology.DataLoaderFacade;
import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.model.Frame;
import edu.stanford.smi.protege.util.LabeledComponent;
import edu.stanford.smi.protegex.owl.model.impl.DefaultOWLIndividual;
import edu.stanford.smi.protegex.prompt.KnowledgeBaseInMerging;
import edu.stanford.smi.protegex.prompt.Mappings;
import edu.stanford.smi.protegex.prompt.ProjectsAndKnowledgeBases;
import edu.stanford.smi.protegex.prompt.actionLists.ActionArgs;
import edu.stanford.smi.protegex.prompt.event.PromptEvent;
import edu.stanford.smi.protegex.prompt.event.PromptListener;
import edu.stanford.smi.protegex.prompt.event.PromptListenerManager;
import edu.stanford.smi.protegex.prompt.operation.MergeClsesOperation;
import edu.stanford.smi.protegex.prompt.operation.Operation;
import edu.stanford.smi.protegex.prompt.plugin.util.KnowledgeBaseFacade;
import edu.stanford.smi.protegex.prompt.ui.SuggestionListPane;
import edu.stanford.smi.protegex.prompt.ui.TabComponent;
import edu.stanford.smi.protegex.prompt.ui.diffUI.DiffTableView;

public class StatsManager {
	/** constants for representing the type of ontology in the mapping that we are representing */
	public final static int SOURCE = 0;
	public final static int TARGET = 1;

	/** the total number of suggestions in the candidate list */
	//private int totalSuggestionCount;
	/** a table mapping ontology concepts to statistics about that node */
	private Hashtable<Cls, NodeStats> frameToSuggestionCount;

	/** reference to suggestions manager */
	private SuggestionsManager mappingManager;

	/** the current root in the treemap */
	private Cls rootCls;

	private int type;

	private static boolean haveAddedActionListener = false;

	public StatsManager(SuggestionsManager mappingManager, int type) {
		this.mappingManager = mappingManager;
		this.type = type;

		this.frameToSuggestionCount = new Hashtable<Cls, NodeStats>();
		populateSuggestionCounts();

		// listen to prompt events
		PromptListenerManager.addListener(new PromptListener() {
			public void operationStarted(Operation operation, PromptEvent event) {}

			public void diffDone(PromptEvent event) {}

			public void diffTableViewBuilt(DiffTableView view, PromptEvent event) {}

			public void diffUIBuilt(JTabbedPane mainPane, PromptEvent event) {}

			public void initializationDone(PromptEvent event) {}

			public void taskComplete(PromptEvent event, boolean interrupted) {}

			public void UIBuilt(TabComponent promptTab, PromptEvent event) {}

			public void operationCompleted(Operation operation, PromptEvent event) {
				// if a merge operation was performed, update our stats
				if (operation instanceof MergeClsesOperation) {
					populateSuggestionCounts();
				}
			}
		});

		if (!haveAddedActionListener) {
			haveAddedActionListener = true;
			addRemoveOperationListener();
		}
	}

	public NodeStats getNodeStats(Cls cls) {
		if (cls == null) {
			return null;
		}
		NodeStats stats = frameToSuggestionCount.get(cls);

		if (stats != null) {
			return stats;
		}
		return new NodeStats();
	}

	/**
	 * Adds an action listener to Prompt's remove operation action so that we can update the trees
	 * and statistics about candidate mappings.
	 */
	private void addRemoveOperationListener() {
		// this is an awful hack, have to find the Remove operation because it is not exposed
		if(TabComponent.getTodoPane() != null) {
			LabeledComponent component = ((SuggestionListPane) TabComponent.getTodoPane()).getLabeledComponent();
			for (Object o : component.getHeaderButtons()) {
				if (o instanceof JButton) {
					JButton button = (JButton) o;
					if (button.getToolTipText() != null && button.getToolTipText().equals("Remove operation")) {
						button.addActionListener(new ActionListener() {
							public void actionPerformed(ActionEvent e) {
								populateSuggestionCounts();
								SwingUtilities.invokeLater(new Runnable() {
									public void run() {
										DataLoaderFacade.getInstance().refreshMapping();
									}
								});
							}
						});
					}
				}
			}
		}
	}

	private Cls getRootCls() {
		Cls rootCls;

		if (type == StatsManager.SOURCE) {
			rootCls = KnowledgeBaseFacade.getMappingSourceKnowledgeBaseInMerging().getKnowledgeBase().getRootCls();
		} else {
			rootCls = KnowledgeBaseFacade.getMappingTargetKnowledgeBaseInMerging().getKnowledgeBase().getRootCls();
		}

		return rootCls;
	}

	/**
	 * Performs the stats calculations for suggestions and nodes.
	 */
	private void populateSuggestionCounts() {
		mappingManager.updateWorkingSuggestions();

		clearFrameToSuggestions();

		calcLocalStats(mappingManager.getWorkingSuggestions());
		calcGlobalStats();
		calcMappingStats();
	}

	/**
	 * Resets all the statistics kept in the frameToSuggestionCount Hashtable
	 */
	private void clearFrameToSuggestions() {
		for (NodeStats stats : frameToSuggestionCount.values()) {
			stats.setLocalCount(0);
			stats.setNumOfNodesWithCandidates(0);
			stats.setNumOfNodesWithMappings(0);
			stats.setNumOfNodesWithOutCandidates(0);
			stats.setTotalCount(0);
			stats.setNumOfSubclasses(0);
			stats.getCandidates().clear();
		}
	}

	@SuppressWarnings("unchecked")
	private void calcLocalStats(ArrayList suggestions) {
		for (int i = 0; i < suggestions.size(); i++) {
			Operation operation = (Operation) suggestions.get(i);

			ActionArgs args = operation.getArgs();
			if (args != null) {
				Cls c1 = null, c2 = null;

				// convert the args to Cls objects if possible
				if (args.getArg(0) instanceof Cls && args.getArg(1) instanceof Cls) {
					c1 = (Cls) args.getArg(0);
					c2 = (Cls) args.getArg(1);
				} else if (args.getArg(0) instanceof DefaultOWLIndividual && args.getArg(1) instanceof DefaultOWLIndividual) {
					c1 = ((DefaultOWLIndividual) args.getArg(0)).getDirectType();
					c2 = ((DefaultOWLIndividual) args.getArg(1)).getDirectType();
				}

				if (c1 != null && c2 != null) {
					if (type == SOURCE) {
						setStats(c1, c2);
					} else {
						setStats(c2, c1);
					}
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	private void calcGlobalStats() {
		for (Enumeration enumerator = frameToSuggestionCount.keys(); enumerator.hasMoreElements();) {
			Cls cls = (Cls) enumerator.nextElement();
			NodeStats nodeStats = getNodeStats(cls);

			if (nodeStats.getLocalCount() > 0) {
				addToParentCount(cls);
			}
		}
	}

	@SuppressWarnings("unchecked")
	private void calcMappingStats() {
		KnowledgeBaseInMerging knowledgeBaseInMerging = ProjectsAndKnowledgeBases.getKnowledgeBaseInMerging(0);
		HashMap mappings = knowledgeBaseInMerging.getWhatBecameOfItMap();

		for (Iterator iter = mappings.keySet().iterator(); iter.hasNext();) {
			Frame frame = (Frame) iter.next();

			if (frame instanceof Cls && !frame.isSystem()) {
				Cls sourceCls = (Cls) frame;
				Cls targetCls = (Cls) Mappings.getWhatBecameOfIt(frame);

				// make sure this is a valid mapping that we care about
				if (targetCls == null) {
					continue;
				}

				Collection sources = Mappings.getSources(targetCls);
				if (sources == null) {
					continue;
				}

				for (Iterator iter2 = sources.iterator(); iter2.hasNext();) {
					Cls clsItem = (Cls) iter2.next();
					if (!clsItem.getKnowledgeBase().equals(frame.getKnowledgeBase())) {
						targetCls = clsItem;
					} else {
						sourceCls = clsItem;
					}
				}

				// if this is the source ontology, change our source
				if (type == SOURCE) {
					sourceCls = targetCls;
				}

				//System.out.println(type + " " + sourceCls.getBrowserText() + " " + targetCls.getBrowserText());

				NodeStats suggestionStats = getNodeStats(sourceCls);
				if (suggestionStats != null) {
					suggestionStats.setNumOfNodesWithMappings(suggestionStats.getNumOfNodesWithMappings() + 1);
					frameToSuggestionCount.put(sourceCls, suggestionStats);

					// propogate this result up the hierarchy
					addMappingsToParents(sourceCls);
				} else {
					System.out.println("could not find stats for " + frame.getName() + " " + frame.getBrowserText());
				}
			}
		}
	}

	private void setStats(Cls cls, Cls candidateCls) {
		NodeStats suggestionStats = getNodeStats(cls);

		suggestionStats.setLocalCount(suggestionStats.getLocalCount() + 1);
		suggestionStats.setTotalCount(suggestionStats.getTotalCount() + 1);
		suggestionStats.setNumOfNodesWithCandidates(1);
		suggestionStats.setNumOfNodesWithOutCandidates(cls.getSubclasses().size() + 1 - suggestionStats.getNumOfNodesWithCandidates() - suggestionStats.getNumOfNodesWithMappings());
		suggestionStats.setNumOfSubclasses(cls.getSubclasses().size());
		suggestionStats.addCandidate(candidateCls);

		frameToSuggestionCount.put(cls, suggestionStats);
	}

	/**
	 * Go through list of superclasses and bubble up statistic results
	 * 
	 * @param cls The Cls with data
	 */
	@SuppressWarnings("unchecked")
	private void addToParentCount(Cls cls) {
		int count = (frameToSuggestionCount.get(cls)).getLocalCount();
		Collection superClasses = cls.getSuperclasses();
		Cls rootCls = getRootCls();

		for (Iterator iter = superClasses.iterator(); iter.hasNext();) {
			Cls parentCls = (Cls) iter.next();

			if (((Frame) parentCls).isSystem() && !rootCls.equals(parentCls)) {
				break;
			}

			NodeStats suggestionStats = getNodeStats(parentCls);

			suggestionStats.setTotalCount(suggestionStats.getTotalCount() + count);
			suggestionStats.setNumOfNodesWithCandidates(suggestionStats.getNumOfNodesWithCandidates() + 1);
			suggestionStats.setNumOfNodesWithOutCandidates(parentCls.getSubclasses().size() + 1 - suggestionStats.getNumOfNodesWithCandidates() - suggestionStats.getNumOfNodesWithMappings());
			suggestionStats.setNumOfSubclasses(parentCls.getSubclasses().size());

			frameToSuggestionCount.put(parentCls, suggestionStats);
		}
	}

	@SuppressWarnings("unchecked")
	private void addMappingsToParents(Cls cls) {
		if (((Frame) cls).isSystem() && !rootCls.equals(cls)) {
			return;
		}

		Collection superClasses = cls.getSuperclasses();
		Cls rootCls = getRootCls();

		for (Iterator iter = superClasses.iterator(); iter.hasNext();) {
			Cls parentCls = (Cls) iter.next();
			if (((Frame) parentCls).isSystem() && !rootCls.equals(parentCls)) {
				break;
			}

			NodeStats suggestionStats = getNodeStats(parentCls);

			suggestionStats.setNumOfNodesWithMappings(suggestionStats.getNumOfNodesWithMappings() + 1);
			suggestionStats.setNumOfNodesWithOutCandidates(parentCls.getSubclasses().size() + 1 - suggestionStats.getNumOfNodesWithCandidates() - suggestionStats.getNumOfNodesWithMappings());
			frameToSuggestionCount.put(parentCls, suggestionStats);
		}
	}

}
